第一部分: Java Web 开发中的基础知识
1. 深入 Web 请求过程
1.1 HTTP 散点知识
Apache, IIS, Nginx, Tomcat, JBoss 都是基于 HTTP 的服务器, HTTP 是应用层的协议, 采用无状态的短连接通信方式
在浏览器里输入 www.baidu.com 敲击回撤的瞬间, 完成了以下操作
浏览器请求 DNS 把域名解析成对应的 IP(域名到IP地址映射: DNS域名服务)
浏览器根据这个 IP 在互联网上找到对应服务器, 发起 GET 请求, 返回数据资源
请求 CDN 寻求静态资源
请求的数据会先到负载均衡, 缓存, 如需要最终才到数据库
HttpClient 工具包 和 curl 命令都能够发起 HTTP 请求, 浏览器正是做了这些工作, 实际上就是建立一个 Socket 通信的过程
要理解 HTTP, 最重要的就是要熟悉 HTTP 中的 HTTP Header, HTTP Header 控制着用户浏览器的渲染行为和服务器的执行逻辑
显而易见, HTTP Request Header 是客户端要告诉服务器的信息, 而 HTTP Response Header 是服务器返回要告诉客户端的信息
Ctrl + F5 的实现逻辑是: 在请求头中加入了 Cache-Control: no-cache 和 Pragma: no-cache, 用于指定所有缓存机制在整个请求/响应链中必须服从的指令
其他用于标识缓存过期的头字段有: Expires, Last-Modified, Etag
1.2 DNS 散点知识
DNS 将域名转化为 IP 的先后关键步骤:
[本机完成] 1) 浏览器缓存 - 2) 操作系统hosts文件
[本机发起请求] 3) LDNS 即在操作系统中配置的 DNS 服务器地址 - 4) 直接请求 Root 域名服务器(全球就9台)
[本机得到反馈结果] 5) 返回给 LDNS 一个主域名服务器地址 gTLD
[本机发起请求] 6) LDNS 去请求 gTLD
[本机得到反馈结果] 7) 返回给 LDNS 网站注册的域名服务器 - 8)请求这个注册的域名服务器得到 IP - 9) LDNS 存储这个映射缓存 - 10) 存到本机(浏览器)
跟踪域名解析结果: dig www.baidu.com [+trace] [+cmd] 、 nslookup
清除域名缓存结果: ipconfig /flushdns 、/etc/init.d/nscd restart
几种域名解析的方式主要分为 A记录、MX记录、CNAME记录、NS记录 和 TXT记录
A记录: 域名对应 IP, 多个域名可以对应一个 IP, 但反之不能
MX记录: 邮件服务器对应的 IP
CNAME记录: 全称是 Canonical Name(别名解析), 一个域名可以设置多个别名
NS记录: 为某个域名指定 DNS 解析服务器, 也就是这个域名有指定的 IP 的 DNS 服务器去解析
TXT记录: 为某个主机名或域名设置说明类的文字
1.3 CDN 散点知识
形象的比喻: CDN = 镜像(Mirror) + 缓存(Cache) + 整体负载均衡(GSLB)
负载均衡的框架通常有三种: 链路负载均衡(DNS), 集群负载均衡(软硬件(LVS + HAProxy + F5)), 操作系统负载均衡(多队列网卡)
CDN 的动态加速技术原理就是在 CDN 的 DNS 解析中通过动态的链路探测来寻找回源最好的一条路径, 从而加速用户访问的效率, 节省带宽
2. 深入分析 Java I/O 的工作机制
2.1 Java I/O 类库的基础架构: java.io
基于字节操作的 I/O 接口: InputStream 和 OutputStream
基于字符操作的 I/O 接口: Writer 和 Reader
基于磁盘操作的 I/O 接口: File
基于网络操作的 I/O 接口: Socket
前两者是数据格式, 后两者是传输方式, 共同影响着 I/O 的效率
字符到字节必须经过编码转换, 这个过程相当耗时
在纯 Java 环境下, Java 序列化能够很好的工作, 但是在多语言环境下, 用 Java 序列化存储后, 很难用其他语言还原出结果。在这种情况下, 还是要尽量存储通用的数据机构, 如 JSON 或者 XML 结构数据, 当前也有比较好的序列化工具支持, 如 Google 的 protobuf 等
适配器和装饰器模式, 适配器是适配接口方便调用(InputStreamReader 适配了 InputStream 对象的 Reader 接口), 装饰器的增加接口功能提升效率(FileInputStream 装饰了 InputStream)
2.2 网络 I/O 的工作机制
影响网络传输的因素: 网络带宽, 传输距离, TCP 拥塞控制(为 TCP 设置一个缓冲区, 让传输方和接收方的步调一致)
Socket 一般都基于 TCP/IP 的流套接字
NIO 是相对于 BIO: 阻塞 I/O 来的, BIO 会导致线程阻塞等待操作系统处理, 在大规模访问量时性能堪忧, 当然可以采取线程池的方法来缓解, 但线程池中线程的创建与回收也需要成本, 大量长连接的请求会一直占用线程池, 线程优先级难以掌控, 众多因素呼唤出了 NIO
NIO 的两个关键词: Channel 和 Selector, Channel 好比传输方式, Selector 是这些传输方式的监控调度, Selector 是一个阻塞线程专门处理连接请求, 监控注册在其上面的 Channel 是否有数据传输发生, 一旦监控到了, 则让 Channel 进行相应的数据传输, 而 Channel 的数据传输发生在另一个非阻塞线程, 这样 Selector 不停的分发 I/O 链路让 Channel 进行通信, 换句话说:
Selector 检测到通信信道 I/O 有数据传输时, 通过 select() 方法取得 SocketChannel, 将数据读取或者写入 Buffer 缓冲区
NIO 提供了两个优化的文件访问方法: FileChannel.transferTo / FileChannel.transferFrom 和 FileChannel.map
2.3 I/O 调优
- 磁盘 I/O 优化
增加缓存, 减少磁盘访问次数
优化磁盘的管理系统, 设计最优的磁盘方式策略, 以及磁盘的寻址策略(这是底层操作系统考虑的)
设计合理的磁盘存储数据块, 以及访问这些数据块的策略, 比如索引
应用合理的 RAID 策略提升磁盘 I/O
- 网络 I/O 优化
- TCP 网络参数调优
1.1 cat /proc/net/netstat: 查看 TCP 的统计信息
1.2 cat /proc/net/snmp: 查看当前系统的连接情况
1.3 netstat -s: 查看网络的统计信息
1.4 ab -c 30 -n 100000 ip:port: 向 ip 并发发送 30 个请求共 100000 个
减少网络交互的次数: 两端设置缓存, 合并请求
减少网络传输数据量的大小: 压缩
尽量减少编码: 提前将字符转化成字节
交互方式的场景选择: 同步与异步/阻塞与非阻塞
3. 深入分析 Java Web 中的中文编码问题
3.1 几种常见的编码
在计算机中存储信息的最小单元是 1 个字节, 即 8 个 bit, 所以能表示的字符范围是 0~255 个, 人类要表示的符号太多, 无法用 1 个字节来完全表示, 要解决这个矛盾必须要有一个新的数据结构 char, 而从 char 到 byte 则必须编码
存储空间与编码效率: ASCII 码, ISO-8859-1, GB2312, GBK(兼容前者, 表示的汉字更多) UTF-16, UTF-8, 其中 UTF-8 是最适合中文的编码方式(不用查码表, 计算可寻, 单字节1汉字节3, 相对于UTF16扩张一倍空间而言, UTF8省), 编码的发生场景常见于存储数据到磁盘或者数据要经过网络传输
不同的编码决定着最终存储的大小, 看的是字节数而不是字符数, 一个 int 用 4 个字节存储, 一个 char 用 2 个字节存储
- 一次 HTTP 请求在很多地方需要编码: URL 的编解码、HTTP Header 的编解码、POST 表单的编解码、HTTP BODY 的编解码
1 | <filter> |